Sense

Containment

Theory

 

 

 

 

An Alternative Mechanism

 

For Handling Scope and Sense


The Sense Alternative Scoping System Author’s manual

 

Copyright 1999, 2000 by Kevin Forchione.

 

This manual is the copyrighted property of Kevin L. Forchione. Permission is granted to distribute this material by digital and/or physical means on the provision that the material is distributed in an unmodified form for non-profit purposes. It is prohibited to redistribute any sub-section from this material separately without the consent of the author. The author makes no warranty of any kind with respect to this material, and disclaims all warranties, including any implied warranties of merchantability or fitness for any particular purpose, or the continued accuracy of this manual for future versions of the product. I have made every attempt to make this documentation accurate, but will not be held responsible for any loss of productivity resulting from errors.

 

Manual version 3.2      (April 2000)

 

Written by Kevin L. Forchione,

c/o Lysseus Dream Ltd.,

1 Bramford Terrace,

23 Westfield Park

Redland

Bristol UK BS6 6LT

 

e-mail:    Lysseus@msn.com

 

If you have any questions or queries about Scope, you may either write or email me.

 

Sense.t is Copyright 1999, 2000 by Kevin Forchione for Lysseus Dream Ltd.

 

 


Contents

 

Preface 5

FREEWARE SOFTWARE LICENSE_ 6

1. Scope Fundamentals 9

Introduction_ 9

1. Access Validation_ 9

2. Usage Verification_ 10

3. List Matching_ 10

Implicit Limitations of Bubble-up / Filter-down Scoping_ 10

2. Sense’s Approach To Accessibility 12

Building The Path_ 12

Containment Lists 12

Path Lists 12

Examining Containment Cases 13

Case 1:      O1 == O2 13

Case 2:     O2 is contained in O1 24

Case 3:     O2 within the same “top level” as O1 46

Case 4:     O2 NOT within the same “top level” as O1 64

Case 5:     O2 NOT within a “location” 86

3. Crossing The Contents Threshold_ 105

The Nature Of Containment 109

A Strict Definition Of “Open” and “Closed”_ 117

Visible, Audible, Olfactory, Tactile, Reachable and Topic Access 135

Basic Definitions Of contentsXXXX Attributes 145

Access Rules 183

4. Validating The Path_ 190

Visualising Action Flow Through The Containment Path_ 204

Visualising Crossing The Contents Threshold_ 209

Visualising Vantage / Target Flow_ 226

Visualising LCC Flow_ 234

Visualising Top-level Location Flow_ 242

Four Checks For Action Passage 247

The Path Validation Sequence 257

Interpreting The Pass-To-XXXX Validations 276

General Meaning of passToObject() 280

General Meaning of passToLocation() 288

General Meaning of passToContents() 294

General Meaning Of passAcrossContents() 298

Connecting Top-level Locations 308

5. Describing Objects Directly Addressed_ 324

Six Degrees Of Containment Separation_ 327

Obtaining Proximity Information_ 344

Extended Description Methods 363

Description Routers 385

6. Describing Objects Indirectly Addressed_ 416

Room Display 419

Passing Senses Between Rooms In Room Display 425

Controlling The Display 446

The isListedXxxxx Attributes 452

The isqXxxx Attributes 462

Tips For Building The Room Display 472

7. Containment Class 485

Redefining The ADV.T Container and Surface Classes 487

Defining A Room_ 542

The Basic Room Display Listing_ 574

Defining A Surface 644

Defining A Container 704

Defining An Openable 743

Defining An Enterable Surface or Container 792

The View From An Enterable Surface or Container 814

The Inside Description Using insideDesc_ 828

Defining A Transparent 946

Defining a Permeable 976

Defining An Enterable Openable 991

The View From An Enterable Openable 1036

Defining A Lockable 1077

Defining A Vehicle 1115

On Boarding and Unboarding Enterables and Vehicles 1360

8. Object Reactions 1380

9. Library-defined Functions and Methods 1400

Appendix: Modifications To ADV.T & STD.T_ 1757

mainRestore(fname) 1761

thing.verifyRemove() 1765

thing.isVisible(vantage) 1769

room.enterRoom(actor) 1773

room.lookAround(actor, verbosity, sroot) 1777

room.nrmLkAround(actor, verbosity) 1781

deepverb.validDoList(actor, prep, iobj) 1785

deepverb.validDo(actor, obj, seqno) 1789

deepverb.validIoList(actor, obj, seqno) 1793

deepverb.validIo(actor, obj, seqno) 1797

die() 1801

Init() 1805

global 1809

 


Preface

 

Sense.t is an extension for HTML TADS’ default parsing/adventuring system contained within ADV.T. Games that use ADV.T can be modified to run with Sense with minimal changes, and receive the immediate benefits of the system.

 

Sense.t provides an enhancement of the accessibility rules provided by ADV.T. The scope() function will return a list of all objects that are valid for a vantage for a particular scope-filter. The path() function will create a scope path between the vantage and an object.

 

To fully understand how the Sense extension works you will need to understand how accessibility works within the ADV.T library. One of the fundamental ideas of TADS is that the accessibility rules are not built into the parser, but are part of ADV.T and adaptable to the requirements of the author. ADV.T provides a basic set of accessibility rules which are efficient and more than adequate for the behaviour of its basic set of classes. But for advanced scoping techniques we must apply an alternate method.

 

Sense came about through my studies of the accessibility mechanisms of ADV.T and the implementation of an accessibility mechanism that allowed for sense passing and non-ancestor specific scoping. It incorporates and builds upon the flexible scope list system established in Scope 2.0 and the rudimentary scope path system of Path 1.0

 

Accessibility governs concepts like visibility (what can be seen) and reachability (what can be taken). As a process it is the first stage of object resolution. It is helpful, when thinking about accessibility, to keep the phrase candidate objects in mind, as the result of the application of accessibility rules should be a list of objects that are valid candidates for final object resolution. As such we are not concerned at this stage with any of the mechanisms of final object resolution.

 

The guiding philosophy behind Sense.t is the same that guided Scope.t and Path.t, and has always been one of least change to the existing library mechanisms. Since accessibility is largely governed through deepverb's validXoList() and validXo() methods, one can simply plug the Sense.t module into an existing game source to replace the ADV.T basic mechanism.

 

A few other points of contact remain, however: deepverb’s cantReach() method, the thing class verifyRemove() and isVisible() methods. These are the minimal requirements to allow an actor inside of an openable to behave reasonably with regard to its scope. Further modifications to allow for room displays are necessary and are documented.

 

Secondly, efficiency is an important consideration. Because scoping is an iterative process attempts have been made to keep the amount of code execution to a minimum. Because the anticipated behaviours of objects is more complex than the basic classes provided in ADV.T somewhat more precise determinations of accessibility are involved which means longer processing time. The added time should, however, appear negligible on most modern machines.

 

Finally, modularity and simplicity of design was aimed for. Sense.t provides a minimal set of functions that meet the requirements of TADS' accessibility mechanism.

 

A full copy of the Sense system, including this manual can be found at Internet location:

 

            ftp.gmd.de in the /if-archive/programming/tads/examples directory

 


FREEWARE SOFTWARE LICENSE

 

This is the Sense Author's Manual. You may use and redistribute this software, free of charge, with the following restrictions:

 

1.      You must include this file and the copyright notice with all copies.

2.      You may not require or collect a fee for copies of this software, or any part of this software, that you give to other people.

3.      You may not include this software with any other software for which a fee is collected.

4.      You may not modify this software in any way, and each copy you make and distribute must be a full and complete copy of the software you originally received.

5.      Anyone to whom you give a copy of this software receives all of the same permissions that you did under this license.

 

You are permitted to create and distribute translations of this software into other layout languages or formats; any such translation shall be subject to the restrictions of this license as well. You may also make hardcopies of the documentation; hardcopies are also subject to these restrictions.

These files are distributed without warranty of any kind, including without limitation any warranties of merchantability or fitness for a particular purpose. THE READER ASSUMES FULL RISK AND RESPONSIBILITY FOR USE OF THESE FILES. UNDER NO CIRCUMSTANCES IS LYSSEUS DREAM, LTD. LIABLE FOR ANY DAMAGES, DIRECT OR INDIRECT, RESULTING FROM USE OF THIS DOCUMENTATION.


About Version 3.2

 

This manual was written in Microsoft Word 2000.

 

 

Acknowledgements

 

Much gratitude is owed to Michael J. Roberts, author of TADS, whose efforts have enabled others to put their dreams and ideas into the works of Interactive Fiction. It is hoped that this manual will provide both instruction and food for the imagination.

 

I would also like to than Irene Callaci for her help in testing the demonstration game, and for her suggestions.

 

The changes to sense.t as of release 2.3.0 were the result of a puzzle posted by Graham Nelson on rec.arts.int-fiction. Changes in the 3.2 release owe a debt of gratitude to Michael J. Roberts for his suggestions on the iorequires for throwVerb.

 


System Requirements

 

Sense requires TADS 2.5.1 or later.

 

Required System Files

 

In addition to requiring HTML TADS the Sense system is composed of the following files:

 

Sense.t           This file contains the path and scoping functions and changes to thing, room, nestedroom, and deepverb necessary to plug Sense into the ADV.T library.

 

 

 

 

 

 


1. Scope Fundamentals

 

 

Introduction

 

After the parser has parsed the noun phrase it utilises several parser hooks to help in object resolution. It is at this juncture that the author can assist the parser in narrowing down the list of objects by applying certain rules that form the framework of behaviour for the model world.

We first examine three of these parser-hook stages, and discuss briefly their impact upon the nature of the model world being constructed.

1. Access Validation

Access validation is used to construct a list of "valid" objects - a list of candidates that conform to the behaviours of the world model rules of accessibility. There are two stages involved in building this list of valid objects, or list V.

    a. validXoList() is the first point of contact the parser has with regard to "accessibility rules". ADV.T takes the approach of generally applying visibility-oriented access rules to the building of the candidates list. This list is then intersected with our original noun phrase N-list to
produce the V-list.

ADV.T creates this list using the "bubble-up/filter-down" scoping method of visibleList() - this basically checks whether an object has certain attributes before allowing iteration down into its contents list. ADV.T tends to use what I call the "hard-structure" approach toward this determination.

 

Hard-structure is based on class-related distinctions, which remain static throughout the game; as opposed to "soft-structure" or attribute-related distinctions, which can be dynamically altered.

For example: the visibleList() function performs the following check to determine if visibility crosses the contents barrier:


if (not isclass(obj, openable)
or (isclass(obj, openable) and obj.isopen)
or obj.contentsVisible)

 

A close examination of the use of contentsReachable() shows that it is defined accordingly:

    thing.contentsReachable = (true)
    openable.contentsReachable = { return self.isopen }

 

which performs precisely the definition of:


if (not isclass(obj, openable)
or (isclass(obj, openable) and obj.isopen))

 

Thus this check in visibleList is functionally equivalent to:

    if (contentsReachable or contentsVisible)

the difference being that an object can modify and override the default value of contentsReachable, but cannot modify its class affiliation. This means that an object is always considered visible by visibleList() regardless of its contentsReachable value.

Hard-structure places the burden of proof within the evaluating function or method; soft-structure places these determinations with the object being evaluated.

    b. validXo: If this method returns true, it means that the object is valid for the current command - in other words, the object is accessible to the actor for the purposes of the command.

For this stage ADV.T applies reachability-oriented access rules through the obj.isReachable() method in building the V list, eliminating those that fail from the list. Exceptions, such as the inspectVerb apply visibility rules through the use of obj.isVisible.

Important Things To Note

It's important to note what information is available to the validXoList() and validXo() methods. In the former, no information is available about the parsed noun-phrase list (N). We are supplied with the actor, preposition, and the other object, if they are known at the time; in the later method we know only the actor, an object from our N-list, and the sequence number of the object in the N-list. We can obtain preposition and other object information only if it is saved from the validXoList() method.

 

In particular, accessibility rules applied in the validXoList() method have a drastic effect upon the nature of scope within our world model.

 

 

This is because without knowledge of the parsed noun list (N) we are, in general, left to build the list based on the actor alone.

While this increases parser efficiency, because of the system of containment used in ADV.T, this usually results in limiting scope to objects within a single room. In most cases this is perfectly reasonable, but it is a major defining restriction. Given the information available the "bubble-up/filter-down" method employed by visibleList() in one form or another is the only practical approach in creating the validXoList() list.

 

Expanding the scope by moving room objects into another container, or by simply doing an object loop, results in increased processing time spent building a list of candidates, many of which are likely to be discarded when the N-list and validXoList() list are intersected.

An alternative approach would be to use validXoList() as a filter only for special verbs that deal with what are likely to be long lists; returning nil for those verbs that rejectMultidobj, etc. The ask/tell/consult verbs might be able to use the method as a means of screening out non-topic objects.

If an author chooses to return (nil) from validXoList then s/he must apply both visibility and reachability rules in the validXo() method -- unless the isVisible() method is allowed to be used by the parser (see below).

 

Otherwise the cantReach() mechanism will produce nonsensical responses, such as "You must open the bottle first" when "I don't see any foo here." is more appropriate. [The parser message "I don't see any foo here" produced in step 3. List Matching indicates that the parser assumes visibility as the minimal level of accessibility. More on this below.]

2. Usage Verification

 

Next the parser takes the objects from the V-list and applies verXo method checking silently to each object for the verb in question, building an L-list of those that pass verification. These are the "silent" parser calls for which TADS is famous. They produce a list of objects for which the action is "logical" (i.e. "can we open the door?", "can we take the foo?") and are based on the state of the object, not on its accessibility to the actor.

3. List Matching

 

The parser then compares our V-list with our L-list. We have the following possibilities:

  1. both lists are empty: We go back to the N-list and run o.isVisible() against each object in the list. If none of the objects passes the isVisible() check then we get the "I don't see any foo here." message. If there are objects which pass the test then the cantReach() mechanism is applied [in ADV.T this is handled by the individual objects; in WorldClass this is handled in the verb].
  2. Logical list is empty: the parser uses only the V-list
  3. Logical list is not empty: the parser uses only the L-list

Things To Note

As mentioned previously, the isVisible() method is instrumental in determining whether the parser will display "I don't see any foo here." Or will use the cantReach() mechanism. Returning (true) from isVisible by default means that cantReach() must be able to handle the message correctly, making it applicable to more than just "reachability". This means that the method must have some way of knowing which accessibility rule the object failed.

 

Implicit Limitations of Bubble-up / Filter-down Scoping

 

The first and most important limitation is that all accessible objects must share the same top-level location and lie along an unbroken path in the object-tree of the vantage. The accessibility mechanisms of ADV.T assume this, and Scope 2.0 enforces this to an even greater degree through its evaluation of the floatingItem class objects.

 

There may be instances where you wish to apply a new accessibility rule for a special case verb. In this instance you need to decide if the bubble up/filter down model of ADV.T is appropriate. It then becomes a matter of creating a new scope-level definition and replacing the isScopedef() function. Further than that you may need to make changes to verification and action methods to round out object behaviour.

 

But what if you have instances when you wish to include an object that is not part of the vantage's containment list into its scope? Sense.t provides a mechanism for this.


2. Sense’s Approach To Accessibility

 

As non-ADV.T libraries, such as WorldClass, have discovered, for the reasons listed above, moving beyond "visibility" / "reachability" involves altering the approach taken by ADV.T.

 

Building a candidates list from validXoList() involves too little information: you either limit scope to some arbitrary containment (i.e. "room") or you duplicate the work done in validXo().

 

Like WorldClass, the Sense.t library extension for ADV.T returns (nil) from validXoList(), making all the objects from our parsed noun list available to validXo().

Because it was the author's desire not to limit access to "visibility" / "reachability" issues Sense.t also returns (true) from obj.isVisible, thus bypassing the parser produced "I don't see any foo here" messages and placing the task of messaging on cantReach().

This places the burden of establishing the accessibility of the world model within the validXo() method. This method knows the actor and the object in question. Because ADV.T is not concerned with issues of "disposition" (i.e. "on", "under", "in", etc) there is no need for the method to know the preposition of the command (and hence have to deal with issues of object disposition as WorldClass does.) The validXo() method can then concern itself with building a path between the actor and the object.

Once you look at it this way, the task is very simple, with only a few questions to be resolved. Clearly the path between the actor and the object consists of a nesting of containment through which that action moves either up or down. [There are five cases that are examined in more detail later in this document.]

To illustrate, imagine the following:

 

An actor is sitting in a chair in his study. Beside him, on a desk is a book of poetry. In the kitchen a pan of soup is cooking on the stove.

 

Building The Path

 

We could construct the path the way NPC movements are determined, linking one room to another by doors, but this is too access-specific a means of connecting actor and object. It might be that our accessibility is not limited to going through the door!

Containment Lists


Instead, adopting a containment approach we produce "bubble-up" lists (clists) for both vantage and target object. This is done the same way that validXoList determines its high-level location which it passes on to the filtering process of visibleList(). 

 

In our example, the actor is the vantage, while the soup is the target. Normally the actor is the vantage, because we are building the path in the direction through which the action will flow.

 

            clist(actor) = [ actor chair study ]

clist(soup) = [ soup pan stove kitchen ]

 

Path Lists

 

We could then simply join our two lists together, working up the one and down the other to form a logical path between actor and object, based on containment without having to examine the precise nature of the containment objects.

Our path for the example cited above from
actor to soup would look like:

    [ actor chair study kitchen stove pan soup ]

And from
actor to book would look like:

    [ actor chair study study desk book ]

In many cases we would find that our list held duplicate objects as we moved up through the actor list and down through the object list. This is especially true for objects within the same location as our actor.

 

If we intersect the two lists we discover whether the objects share a common containment. Since we have "bubbled-up" for both objects then the first element of the intersection will be the lowest level of common containment, or lowest-common containment (lcc). If we have an lcc then we can join the lists from this point, shortening the path.

The path is then:

    [ actor chair study desk book ]

with the lcc being [ study ].

Between the containment lists and the path list we now know:

 

1.      the vantage object

2.      the target object

3.      the logical containment path

4.      the lowest common containment (lcc), if one exists.

5.      the top-level locations for both vantage and target

 

From this we can perform a series of validity checks on each location in our path for each kind of accessibility we are interested in.

If we have two levels of accessibility we wish to check for, say for example, visibility and reachability, we can move across our path, checking each object for visibility. If any of them fail the test then we can return (nil) from validXo() and handle the message in cantReach().  If every object passes our visibility check then we move across the path, checking each object for reachability, and repeating the process for each kind of access we wish to determine.

This is the essence of the Sense approach, which has been designed to give ADV.T users an alternative model world, which in its default operation should behave very much like ADV.T itself, but is extensible and adaptable to such concepts as "sense" passing, containment, and non-containment specific accessibility.

 

Examining Containment Cases

 

Before we continue with path validation it may be helpful to visualise the various cases of containment that are possible within a single-contents containment system. There are only five cases we need to examine.

 

  1. Case 1:           O1 == O2
  2. Case 2:           O2 is contained in O1
  3. Case 3:           O2 within the same “top level” as O1
  4. Case 4:           O2 NOT within the same “top level” as O1
  5. Case 5:           O2 NOT within a “location”

 

 

 

Case 1:           O1 == O2

 

In the simplest case the two objects are the same object.

 

 


 

 


L1 == L2 == {O1 C1 C2 R1 }

L1 ∩ L2 == L1

L’ == { O1 }

 

Validation Sequence:

 

O1                 pass-to-object

 

 


Case 2:           O2 is contained in O1

 

 


 

 


L1  == { O1 R1 }

L2  == {O2 C1 O1 R1 }

L1 ∩ L2 == { O1  R1 }

L’ == { O1 C1 O2 }

 

Validation Sequence O1 NOT EQUAL LCC

 

O1           pass-to-object / pass-to-location

C1                  pass-to-object / pass-across-contents / pass-to-location

O2                 pass-to-object

 

Validation Sequence O1 EQUAL LCC

 

O1           pass-to-object / pass-to-contents

C1                  pass-to-object / pass-across-contents / pass-to-contents

O2                 pass-to-object

 


Case 3:           O2 within the same “top level” as O1

 

 


 

 


L1  == { O1 C1 C3 R1 }

L2  == {O2 C2 C3 R1 }

L1 ∩ L2 == { C3  R1 }

L’ == { O1 C1 C3 C2 O2 }

 

Validation Sequence

 

O1                 pass-to-object / pass-to-location

C1                  pass-to-object / pass-across-contents / pass-to-location

C3                  pass-to-object / pass-to-contents

C2                  pass-to-object / pass-across-contents / pass-to-contents

O2                 pass-to-object

 


Case 4:           O2 NOT within the same “top level” as O1

 

 


 

 


L1  == { O1 C1 C2 R1 }

L2  == {O2 C3 C4 R2 }

L1 ∩ L2 == Ǿ

L’ == { O1 C1 C2 R1 R2 C4 C3 O2 }

 

Validation Sequence

 

O1                 pass-to-object / pass-to-location

C1                  pass-to-object / pass-across-contents / pass-to-location

C2                  pass-to-object / pass-across-contents / pass-to-location

R1                  pass-to-object / pass-across-contents / pass-to-location

 

R2                  pass-to-object / pass-across-contents / pass-to-contents

C4                  pass-to-object / pass-across-contents / pass-to-contents

C3                  pass-to-object / pass-across-contents / pass-to-contents

O2                 pass-to-object

 

Case 5:           O2 NOT within a “location”

 

 

 

 


 


L1  == { O1 C1 C2 R1 }

L2  == {O2 }

L1 ∩ L2 == Ǿ

L’ == { O1 C1 C2 R1 O2 }

 

Validation Sequence

 

O1                 pass-to-object / pass-to-location

C1                  pass-to-object / pass-across-contents / pass-to-location

C2                  pass-to-object / pass-across-contents / pass-to-location

R1                  pass-to-object / pass-across-contents / pass-to-location

O2                 pass-to-object

 


3. Crossing The Contents Threshold

 

Before proceeding on to path validation it is probably a good idea to backtrack slightly, and look at what it means to be a containment object.

 

The Nature Of Containment

 

Most object-oriented libraries use a single-location approach to containment. In this approach an object has one location and appear in the contents list for at most only one object at any given time. Even floating items, which have code-determinant locations resolve to one location and one contents list. Even libraries that employ multiple contents list approach resolve objects to one location at any given time.

 

Given a game object, such as a sack, it is possible to determine exactly whether an object is inside the sack or not. It is reasonable to expect to be able to take the object from the sack if the sack is open, but not if the sack is closed. Likewise we may examine the object if the sack is transparent, but not otherwise. These are the results of the “open” and “closed” nature of the sack.

 

But what exactly does it mean to be “open” or “closed”?

 

A Strict Definition Of “Open” and “Closed”

 

OPEN:            When an object is “open” we are indicating that for a specified action there is access by the object’s location to the object’s contents; and conversely, access by the object’s contents to the object’s location.

CLOSED:       When an object is “closed” we are indicating that for a specified action there is no access by the object’s location to the object’s contents; and conversely, no access by the object’s contents to the object’s location.

 

These definitions are very precise and are action-dependent. Most game objects are assigned an attribute that indicates a status of being open or closed and is not action-dependent. Later in the chapter we shall develop terminology to express these determinations.

 

For example, an apple inside of a closed glass jar is still visible to the player, even though it is not immediately reachable. So the precise determination of whether an object is “open” or “closed” is not as simple as it first appears.

 

The ability of an action to pass from an object’s location to its contents, or conversely from its contents to its location is what we mean by crossing the contents threshold.

 

 


 

 

 


But actions, such as <<take>> or <<examine>> or <<listen to foo>> generally have certain basic sensory requirements: is the object visible? Is the object reachable? Rather than providing tests for each possible action defined in the library (which could be over 100 actions!) we can boil them down to a handful of senses required to perform them. For example, <<take apple>> and <<eat apple>> both require that the apple is reachable by the player in order for the action to succeed.

 

Rather than use the terms “open” and “closed” we can now talk about whether an object’s contents are “reachable”, “visible”, “smellable”, “audible”, etc.

 

Visible, Audible, Olfactory, Tactile, Reachable and Topic Access

 

Sense works within the conceptual framework of visibility, audibility, olfactory, touchability, reachability, and topicality, but these are defined in terms of the ability of an object to access another object’s contents using the strict definition of open and closed as interpreted appropriately for each form of access.

 

Visibility, audibility, olfactory, and touchability attempt to simulate the four senses (five including taste, which extends touchability). These definitions are exclusive of the presence of any medium for conducting or carrying the sense. For example, visible access is affirmed for an object even in the absence of light.

 

Reachability is more abstract in that it pertains to the direct object’s ability to “touch” the indirect object even when the actor is unable to do so. For example, an actor shooting an arrow at a target does not require touch access to the target, but the arrow does. The arrow must be able to “touch” the target before the action of striking the target can be completed.

 

Topicality is the ability of an actor to access an object that is otherwise non-sensible. These objects usually function as the indirect objects of conversation.

 

Basic Definitions Of contentsXXXX Attributes

 

Once we have built our scope path L’ we need to determine if the action can travel along the path. Scope defined 5 attributes each object inherits from thing class to help make this determination. These act as two-way streets determining if the object allow passage across the contents threshold.

 

contentsVisible: this method determines if the contents of an object are visible from the outside. But this is basically a two-way street. If the contents are visible from the outside, then the outside is visible from the contents.

 

contentsAudible: this method determines if the contents of an object are audible across the contents threshold. By default openable objects return true for contentsAudible, regardless of whether they are open or closed.

 

contentsOlfactory: this method determines if the contents of an object are smellable across the contents threshold. By default openable objects return true for contentsOlfactory, regardless of whether they are open or closed.

 

contentsTouchable: this method determines if the contents of an object are touchable from the outside. This, too, is a two-way street. If the contents are touchable from the outside, then the outside is touchable from the contents.

 

contentsReachable: this method determines if the contents of an object are reachable from the outside. This, too, is a two-way street. If the contents are reachable from the outside, then the outside is reachable from the contents.

 

The table below shows the return values for the three classes in ADV.T / Sense.t that directly define contentsXXXX attributes. Each object is composed of the 5 attributes, the attributes can return either true, nil, or self.isopen, which gives us 125 possible class combinations. Fortunately, for all practical purposes we can reduce this number to a mere handful.

 

Class

contentsVisible

contentsAudible

contentsOlfactory

contentsTouchable

contentsReachable

thing

True

True

True

True

True

openable

Self.isopen

Self.isopen

Self.isopen

Self.isopen

Self.isopen

containment

if (Self.isTransparent ||Self.ispermeable) True; else Self.isopen;

if (Self.ispermeable) True; else Self.isopen;

if (Self.ispermeable) True; else Self.isopen;

Self.isopen;

if (Self.ispermeable True; else Self.isopen;

transparentItem

True

Self.isopen

Self.isopen

Self.isopen

Self.isopen

permeable

True

True

True

Self.isopen

True

 

thing

A thing has no contents, so in a sense we cannot talk about its behaviour when crossing the contents threshold. But for the sake of completeness thing class objects return true for each of the 5 contentsXXXX attributes. These values are inherited by all objects that extend thing class.

 

openable

Access for each of the 5 contents threshold attributes is determined by whether the object isopen == true or nil. Thus the contents of an openable are completely inaccessible when the openable is closed. Bank safes, shoe boxes, mail boxes, and other openable containers make up this class.

 

containment

The containment class is polymorphic in that it depends upon attributes to determine the characteristics of its containment. Thus the containment class object can behave like an openable, transparentItem, permeable, room or nestedroom.

 

transparentItem

The transparentItem class comprises objects whose contents are still visible even when the container is closed. These can be glass bottles, plastic boxes, etc. A transparentItem does not allow sound or smell to travel across its contents threshold when closed, nor can objects be projected into it when closed.

 

permeable

The permeable class behaves very much like a cage. When the permeable is closed objects inside can be seen, heard, and smelled, and objects outside projected into the permeable, but the contents of the permeable cannot otherwise be touched. However, when open the permeable allows complete access, just as the openable and transparentItem classes do.

 

Access Rules

 

Sense defines access_rules for use in cases where contents threshold evaluation is necessary. These functions receive only 1 parameter: the object to be evaluated, and return true if the object passes the test; otherwise nil. The access_qXXXX rules are used in the display process of extended sense passing.

 

See Appendix for details.

 


4. Validating The Path

 

Checking whether or not we can cross the contents threshold is not enough to determine whether the vantage has access to the target object. The contentsXXXX attributes act as two-way streets they basically apply the rule:

 

 

           

If I can access you, you can access me.

 

 

But there may be occasions where we don’t want this reflexivity to hold true. Without some kind of check the path  O1 => O2 is “open” contingent only upon the action and the “open / closed” nature of the attributes of the objects along the path.

 

For instance, because top-level locations are defined as “open” (for technical reasons), it would be possible to reach into other rooms and take things, but not be able to take things from closed containers that are in those other rooms.

 

 

Visualising Action Flow Through The Containment Path

 

It’s easiest to visualise the passage of action along the containment path as a sort of chain stretched between the vantage to the target object or as a pipe through which the action flows like water. Thus, with an action such as <<listen to foo>> the action flows through the actor to the actor’s location, and then along the containment path to the foo’s location and finally to the foo.

 

 

Visualising Crossing The Contents Threshold

 

The following diagram represents action “flowing” from O1 through O2 to O3 across the contents threshold of O2. Since the contentsXXXX attributes are reflexive the action can flow equally in the opposite direction.

 

If we create a path list to represent the flow of the action we must first decide which object is the vantage and which is the target. In other words, a path is defined to evaluate the movement of an action in one direction only. Suppose we examine the path of action from O1 through O2 to O3.

 

[ O1 O2 O3 ]

 

Since the action is originating with the O1 object we must determine whether it can pass to this object’s contents. We are not concerned with whether the action can cross the object’s contents threshold. To make this clear, suppose the actor is reaching across a table for a steel box. We don’t care if the action can cross the contents threshold of the target (steel box), and indeed we would have a problem if the box were closed.

 

Likewise, it could be conceived that a “closed” object might initiate the action, and so we would not want to concern ourselves with whether the action could cross the contents threshold of the vantage (in most cases the actor).

 

We can envisage the passage of the action across the containment path from O1 to O3 thus:

 

       O1 pass-to-contents O2 cross-the-contents-threshold O2 pass-to-contents O3

 

Moving across the containment path in the opposite direction from O3 to O1 would be represented thus:

 

       O3 pass-to-location O2 cross-the-contents-threshold O2 pass-to-location O1

 

Along with each of these checks, we will include a check to determine if the object itself will allow the action to pass. While this check may seem unnecessary, it can be very useful, especially for the target object.

 

 

Visualising Vantage / Target Flow

 

 

From the diagram it is obvious that the flow of action for the vantage may involve a pass-to-location or a pass-to-contents, depending upon whether the target object is within the vantage or outside of it. An easy way to determine this is by checking the target containment list. If the vantage is within the target containment list then action flows along pass-to-contents; otherwise it flows along pass-to-location.

 

The vantage is a special case, in that we check the pass-to-object validity, but we are not concerned with whether the vantage is “open” or “closed”.

 

It makes little sense to check for pass-to-location, pass-to-contents, or even pass-across-contents-threshold for the target object. Instead, for the author to exercise any particular control over the access rules for this object they must use pass-to-object alone.

 

 

Visualising LCC Flow

 

 

In this case the disposition of the lowest common containment object (whether it is open or closed, transparent or not, etc) is irrelevant to the flow of the action, since the action is not crossing the contents threshold of the lowest-common containment object.

 

Because we have not crossed the contents threshold the meaning of pass-to-location and pass-to-contents become ambiguous. It is cleaner, therefore to perform a simpler check upon the lowest common containment: pass-to-object. While much of the time this will simply return true, there could be occasions when an author wishes to limit access within the lowest common containment object. This could be the case when the player is “suspended within an orange cloud” or within an unlit room.

 

But once we’ve accepted this check it also begins to make sense to check the action’s eligibility to pass-to-contents.

 

 

Visualising Top-level Location Flow

 

The flow of action from the vantage to the target may appear to pose a couple of problems at first.  The flow of action takes exactly the same path as it does when an lowest-common containment exists. But what happens at the top-level locations, where we have a mathematical discontinuity point?

 

At this point WorldClass resolves the problem by providing a TOP object into which all other objects are moved. It also uses a NIL object, which is also within TOP, to hold objects that have been moved out of the object-tree. Sense takes a simpler approach. It skips over the point of discontinuity in a manner that is analogous to the determination of the derivative in calculus. Can the action pass-to-location at C1; and specifically is the pass-to-location indicated as C2? Can the action pass-to-object at C2? Finally, can the action pass-to-contents at C2? Thus we treat the evaluation the same as the case where a lowest-common containment exists.

 

 

Four Checks For Action Passage

 

Thus each object in our path must pass at least one of the four action checks:

 

a.      action can pass to object

b.      action can pass to location

c.      action can pass to contents

d.      action can pass across the object’s contents threshold

 

 

The Path Validation Sequence

 

The Sense.t passToLocation(), passToContents() and passToObject() and passAcrossContents() methods are simple checks to determine whether or not the object will allow scope to pass through it.  A path is validated moving the access_filter across the path list from vantage to target (i.e. from element[1] to element[n], where n is the length of the list). Although the rules may appear complicated, they really are not. A pseudo-code representation boils it down to the following checks:

 

For each object in path

{

pass-to-object

 

if (o == target) continue

 

if (o != vantage && o != lcc)

       pass-across-contents

 

if (find(clist(target), o))

       pass-to-contents

else

       pass-to-location

}

 

Interpreting The Pass-To-XXXX Validations

 

Each thing-class object inherits pass-to methods that determine the accessibility of the action as it passes along the containment path. For most objects these methods simply return true, indicating that access is unimpeded. Because these methods are used to define the nature of the failed access it is important to understand what Sense expects to be happening when one of these methods fails.

 

General Meaning of passToObject()

 

This method is new to Sense 3.0, and is checked for every object along the path. It represents a failure of access to the object itself and can best be thought of in terms of redirecting the action to this object. For example, suppose we have a ball inside of a closed glass bottle that is sitting upon a shelf. The action <<take ball>> would return “You must first open the bottle” if we validated the path because it would fail at bottle.passAcrossContents().

 

But suppose we want to make the shelf so high that the actor cannot reach it. We could code this in TADS using the xobjCheck() and xobjGen() methods the same as for distantItem objects, but there’s a problem. Because action doesn’t flow along the containment path under ADV.T we have no way of controlling actions directed to objects that are in the distantItem.contents list. We are left with two choices: either coding the restriction in actor.roomAction() or in the target object itself (we could code the validation in verbAction() but this is even worse for O-O design.). In the first solution, problems arise, however, when the action is coming from a remote location (as in a composite room); in the second solution, we are forced to put the same code into every object which might conceivably end up on or in the distantItem.

 

But because Sense validates the action along the containment path we can use passToObject() to block a specified action at a certain point along that path. This means that action will fail for the object and every object within its contents. Thus, in our example, we might allow <<throw>> and <<inspect>> access through shelf.passToObject, while failing other actions, if we want to create a high shelf that objects can be throw onto or inspected, but cannot be reclaimed.

 

General Meaning of passToLocation()

 

Suppose the Alice is sitting upon the mantelpiece in Through The Looking Glass. What can she reach? We can use this method to prevent actions from passing to an object’s location. We might want to limit access to visibility, for instance, or for getting down onto the chair.

 

This method is also used to control accessibility from top-level locations. Ordinarily rooms are defined as top-level locations, and accessibility does not pass beyond the room. But by using this method we can pass the action along to other locations, thus skipping over the “discontinuity point” of the path list.

 

General Meaning of passToContents()

 

Analogous to passToObject, this method can be used when you want to be able to access an object, but not its contents. For example a very high wardrobe might be touchable, but you may be unable to reach anything that is on top of it.

 

General Meaning Of passAcrossContents()

 

Sense 3.0 has redefined the meaning and use of the passAcrossContents() method. In earlier versions this method was used only in conjunction with the lowest common containment (see chart next chapter), which now uses the passToObject() method.

 

This method represents the pass-across-contents-threshold as defined above. Its default definition is thus:

 

        return runRoutine(self, filter);

 

which is the same as (return self.contentsXXXX). This was done to make the passToXXXX methods the central point of accessibility control. Previous versions of Sense.t required special modifications of the contentsXXXX code for making access_filter exceptions. This is no longer necessary, and all exceptions can now be handled in the passToXXXX methods.          

 

Connecting Top-level Locations

 

Passing action across top-level locations poses a special problem. The pass-to-location check provides the most logical point at which to validate access across top-level locations, but the coding can become cumbersome, especially if we need to connect several locations.

 

In the room class four special xxxxInto methods are defined to provide a bridge between top-level locations:

 

 

These methods must return one of three values: a top-level location object, a list of top-level location objects, or nil.

 

The room.passToLocation() calls the retrieveLocs() method to determine if the location indicated by the xxxxInto method is the top-level location of the target object. If this is the case then retrieveLocs() returns true; otherwise retrieveLocs() returns nil.

 


5. Describing Objects Directly Addressed

 

 

Six Degrees Of Containment Separation

 

This concept deals with the idea that an object’s description is relative to the positions of the viewer and the object itself and what kinds of senses are involved.

 

For example (in general), an object on a chair with the actor is in closer proximity to the actor than the same object would be within the room. While this may not be true in every case, knowing the relative proximity of the actor and target object can help in the formulation of a reasonable description of the object.

 

There are the following six degrees of proximity:

 

Proximity

Description

Reflexive

The actor and the object are one and the same. This is the case where the action is self-reflexive. You can’t get closer than this.

Possessive

 

The next closest proximity. The actor has the object in its possession

Contained

Lowest Common Containment is the target object. The actor is within the target object, such as a box.

Immediate:

Lowest Common Containment, not the Room.

The actor and the object share the same containment, such as a chair or a box

Local

Lowest Common Containment is the Room

The actor and object share the same top-level location.

Remote

No Lowest Common Containment

The actor and object are in different rooms that are linked together

 

 

Obtaining Proximity Information

 

Sense.t provides a function to help determine the proximity of vantage and target: proximity(vantage, target); This function takes the vantage and target as arguments and returns a list consisting of the following elements:

 

       [ PRX_XXX [ access_filter list] ]

 

Where the first element is a PRX_XXXX flag is one of the following values:

 

PRX_REFLEXIVE:       The vantage is also the target

PRX_POSSESSIVE:      The vantage possesses the target

PRX_CONTAIND:        The vantage is contained within the target

PRX_IMMEDIATE:       Both vantage and target share a lowest-common containment that is not a top-level room (i.e. a chair or bed, etc.)

PRX_LOCAL:           Both vantage and target share a lowest-common containment that is a top-level room (i.e. the Foyer)

PRX_REMOTE:          The vantage and target do not have a lowest common containment (i.e. They’re in separate rooms.)

 

The second element is a list of all the access_filters valid for the path between vantage and target object (i.e. &access_visible, &access_audible, &access_olfactory, &access_reachable).

 

If an access_filter is passed then the method returns the PRX_XXXX flag for the proximity between the object and the vantage if the path is valid for that access_filter; otherwise it returns nil.

 

Extended Description Methods

 
The various methods and functions used in room listing descriptions and sensing actions automatically determine which message to display, based on the degree of containment as calculated by proximity() as follows:
 

 

 

 

sdesc

Short description for the object when it is in the same room as the actor – default definition

sdesc_reflexive

Short description for the object when it is the same as the actor.

sdesc_possessive

Short description for the object when it is the possession of the actor.

sdesc_contained

Short description of the object when the actor is inside of it.

sdesc_immediate

Short description of the object when the actor and object share a common containment that is not a top-level room: i.e. a chair or a bed.

sdesc_remote

Short description of the object that is not in the same top-level location (i.e. room) as the actor.

 

This same schema is used for adesc, thedesc, ldesc, smelldesc, and listendesc. All of the special extensions default to their root description, as follows:

 

sdesc_xxxx                 defaults to sdesc

adesc_xxxx                 defaults to adesc

thedesc_xxxx              defaults to thedesc

ldesc_xxxx                  defaults to ldesc

listendesc_xxxx           defaults to listendesc

smelldesc_xxxxx         defaults to smelldesc

 

Description Routers

 

Description routers exist to help determine which extended description method to apply and should be used in message generations in place of the usual description attribute and can be imbedded (placed in <<>>) just like any other description attribute. For example, instead of calling ldesc you should call ldescR, which will then determine which of the ldesc_xxxxx attributes apply. The messages are built up recursively by the router, so that thedescR calls sdescR and so forth. For example:

 

            “\^%You% see nothing unusual about <<self.thedescR>>. “

 

will ensure that an extended sense message attribute is used to describe the object. If none is found to match the proximity then the router will move up and down the containment levels until it finds a message to display.

 

The following table displays the default method, the router, and the list of extended description methods corresponding to each proximity flag.

 

Default

Router

PRX_REFLEXIVE

PRX_POSSESSIVE

PRX_CONTAINED

PRX_IMMEDIATE

PRX_LOCAL

PRX_REMOTE

sdesc

sdescR

sdesc_reflexive

sdesc_possessive

sdesc_contained

sdesc_immediate

sdesc

sdesc_remote

adesc

adescR

adesc_reflexive

adesc_possessive

adesc_contained

adesc_immediate

adesc

adesc_remote

thedesc

thedesR

thedesc_reflexive

thedesc_possessive

thedesc_contained

thedesc_immediate

thedesc

thedesc_remote

pluraldesc

pluraldescR

pluraldesc_reflexive

pluraldesc_possessive

pluraldesc_contained

pluraldesc_immediate

pluraldesc

pluraldesc_remote

ldesc

ldescR

ldesc_reflexive

ldesc_possessive

ldesc_contained

ldesc_immediate

ldesc

ldesc_remote

listendesc

listendescR

listendesc_reflexive

listendesc_possessive

listendesc_contained

listendesc_immediate

listendesc

listendesc_remote

smelldesc

smelldescR

smelldesc_reflexive

smelldesc_possessive

smelldesc_contained

smelldesc_immediate

smelldesc

smelldesc_remote

heardesc

heardescR

heardesc_reflexive

heardesc_possessive

heardesc_contained

heardesc_immediate

heardesc

heardesc_remote

touchdesc

touchdescR

touchdesc_reflexive

touchdesc_possessive

touchdesc_contained

touchdesc_immediate

touchdesc

touchdesc_remote

tastedesc

tastedescR

tastedesc_reflexive

tastedesc_possessive

tastdesc_contained

tastedesc_immediate

tastedesc

tastedesc_remote

thrudesc

thrudescR

thrudesc_reflexive

thrudesc_possessive

thrudesc_contained

thrudesc_immediate

thrudesc

thrudesc_remote

readdesc

readdescR

readdesc_reflexive

readdesc_possessive

readdesc_contained

readdesc_immediate

readdesc

readdesc_remote

heredesc

heredescR

heredesc_reflexive

heredesc_possessive

heredesc_contained

heredesc_immediate

heredesc

heredesc_remote

actorDesc

actorDescR

actorDesc_reflexive

actorDesc_possessive

actorDesc_contained

actorDesc_immediate

actorDesc

actorDesc_remote

 

 

The router will first determine the proximity and check to see if the object has the corresponding extended description attribute. If the object doesn’t then it checks to see if it has the next highest level of proximity, moving from reflexive to remote. If we reach the end of the list the router will begin working down the list from the point where proximity was determined. Since all sense default messages are defined at the local level all description messages will eventually default at the local value.

 

 


6. Describing Objects Indirectly Addressed

 

 

Room Display

 

Building and validating a scope path between a vantage and a target object, as we have seen, is a fairly straightforward affair. In that respect <<take the ball>> is handled roughly the same way whether we have a lowest common containment or not.

 

But building the room display, especially in the case where senses are passed from one top-level location to another, is slightly more complicated. The procedure can be outlined as follows:

 

Passing Senses Between Rooms In Room Display

 

    1. Loop through a list of top-level locations and build and evaluate the path derived for each occurrence and the actor.
    2. If the path is valid then build a bubble-scope list of the objects within the room.
    3. Check the validity of this list by building a path between the actor and each object in the list and then validating the path. Only those objects that pass are kept in our list and displayed.

 

 

First building a path from actor to other room locations:

 

            Actor çè other room location

 

In Sense 3.0 the seeInto, hearInto, smellInto, and reachInto methods are used to form the bridge from the top-level location of the vantage to the top-level locations to which we are passing the action. The path between the vantage and this other room are validated in order to save processing time. If the passToLocation() method of the top-level location of the actor returns nil then we continue with the next top-level location in the xxxxInto list. If there are no more locations then the process is finished.

 

Once we have a valid path between the actor and an other room top-level location we must create a list of valid objects within the room. We do this using a bubble scope routine that makes its determinations based on a contents threshold check and a check for “quiet” containers. A check for “quiet” containers at this stage is necessary because we are doing a room display, and not determining simple accessibility.

 

The next step is to validate the path between the actor and each object.

 

            Actor çè object

 

This is done so that the other pass-to checks can be done on the target object. It may seem like a lot of work, but it logically guarantees that the display produced by an action such as <<smell obj>> and the same object presented by the global action <<smell>> have the same behaviours.

 

Controlling The Display

 

Sense generates room displays in a flexible manner. Each room has a nrmXxAround() and xtndXxAround() method for customising the room description when the room is being sense as part of the actor’s containment list, and when it is not.

 

In addition the isListed and isqxxxx attributes used to control room listing display in ADV.T are paralleled in Sense.t

 

The isListedXxxxx Attributes

 

Every object inheriting from thing class is provided with the following listing attributes:

 

·         isListed: used the same as in ADV.T. Determines whether the object appears in room listings for visible scope.

·         isListedAudible: determines whether the object appears in room listings for audible scope.

·         isListedOlfactory: determines whether the object appears in room listing for olfactory scope.

 

For example, if an object defines a smelldesc it will not appear in a nrmSmAround() or xtndSmAround() listing unless it returns true for isListedOlfactory. On the other hand, the object will appear in a the nrmSmAround() or xtndSmAround() listing even if it does not define a smelldesc directly, if it’s isListedOlfactory attribute returns true.

 

The isqXxxx Attributes

 

Sense.t defines 3 attributes that control whether an object displays its contents in a room listing or is “quiet”.

 

·         IsqVisible: The object does not display its contents for visibility in room displays.

·         IsqAudible: The object does not display its contents for audibility in room displays.

·         IsqOlfactory: The object does not display its contents for olfaction in room displays.

 

Both the qsurface and qcontainer class definitions return true by default for all three isqXxxx attributes.

 

Tips For Building The Room Display

 

A perusal of Sense reveals that building the room display is a complicated affair. By coordinating the various scoping and listing attributes, as well as modifying the listing mechanisms themselves an author has a lot of flexibility – and getting all the various aspects of room display to work in harmony takes some practise.

 

Some tips:

 

·         If you want an object to display in room display listings, it must have its isListedXxxx attribute return true. You can toggle this attribute to your advantage.

 

·         Actors inherit from qcontainer, and therefore hide their contents. However, Sense.t movableActor defaults to returning true for isqAudible and isqOlfactory. This means that an NPC’s possessions will automatically appear in room descriptions if they have the isListedXxxx attribute. This also can be toggled to your advantage.

 

·         Containers, by default do not prevent sense passing when closed. This can be toggled in the individual object or as a class.

 

 

7. Containment Class

 

Redefining The ADV.T Container and Surface Classes

 

By defining USE_CONTAINMENT at the top of their game source an author can tell the compiler to implement the containment class which will then be used in place of the standard ADV.T container and surface classes. If you do not wish to implement this definition of containment simply do not define USE_CONTAINMENT.

 

The containment class implements a POLYMORPHIC object class that consolidates the behaviours from the following ADV.T classes:  

 

 

This class allows the author to easily define containment objects possessing different behaviours, all from one base class. The class is polymorphic, in that the containment behaviours of an object can be transformed into that of another by simply changing the value of a few attributes.

 

To illustrate the flexibility of this class let’s develop and extend the sample game provided by the TADS workbench.

 

#define USE_HTML_STATUS

#define USE_HTML_PROMPT

#define USE_CONTAINMENT

 

First we indicate that the game is to use the html status and prompt as well as the common sense approach for sense.t. Next we include the TADS adventure and standard libraries.

 

#include <adv.t>

#include <std.t>

 

The sense.t class provides an alternate scoping mechanism to that of ADV.T, as well as implementing sense-passing facilities. The containment.t file defines the containment class, as well as implementing some refinements to boarding and unboarding.

 

#include <sense.t>

 

To make things a little simpler, we #include the doors.t module, which defines a door class.

 

#include <doors.t>

 

We want to compile the game using the C-style operators, so we indicate this with:

 

#pragma C+

 

And we turn the html interpreting on:

 

replace commonInit: function

{

    /* display the special code sequence to turn on HTML recognition */

    "\H+";

}

 


Defining A Room

 

Now that all the preliminaries are finished we are ready to code game objects. The most natural place to begin is with the startroom.

 

startroom: containment

    lightsOn = true

    sdesc = "Entryway"

    ldesc =

    {

        "This large, formal entryway is slightly intimidating:

        the walls are lined with somber portraits of gray-haired

        men from decades past; a medieval suit of armor";

 

        /* if the axe is in the armor, list it specially */

        if (axe.isIn(suitOfArmor))

            ", posed with battle axe at the ready,";

 

        " towers over a single straight-backed wooden chair.

        The front door leads back outside to the south.  A

        hallway leads north.";

    }

    north = hallway

    south = frontDoor

    out = frontDoor

;

 

The only difference between this definition and the one provided by that of the TADS workbench sample is the inheritance from containment class and the lightsOn attribute, which indicates that this is a lit room. If we wished to make this a “darkroom”, one that is dependent upon a lightsource we would leave this attribute as nil.

 

 

By default all containment class objects behave as “darkrooms” requiring a lightsource for lighting. To make a containment class object behave as though it were self-lit set the lightsOn attribute to true.

 


The Basic Room Display Listing

 

When a player does a <<look>> or enters a room a description of the room and its contents is generated. The basic containment class room display listing is composed of the following parts:

 

a.      The room short description (sdesc)

b.      The room long description (ldesc)

c.      Actor descriptions

d.      A sentence-style listing of the room contents (listcont())

e.      A sentence-style listing of objects on or in any room contents containers (listcontcont())

f.        A firstseen description (first-time only)

 

 

Entryway

       This large, formal entryway is slightly intimidating: the walls are lined with somber portraits of gray-haired men from decades past; a medieval suit of armor, posed with battle axe at the ready, towers over a single straight-backed wooden chair. The front door leads back outside to the south. A hallway leads north.

       Joe is here, looking for lerts.

       You see a chess board, a lidless ceramic jar, and a bag here. Sitting on the chess board is a red queen. The lidless ceramic jar seems to contain a candle (providing light).

 

Our first game object is the suit of armor. Although the armor has an isqcontainer attribute it is defined by the workbench sample code as a fixeditem, and not as a surface or container. We do not need to make any changes to its definition. Neither do we need to modify the definition of the axe or the portraits.

 

suitOfArmor: fixeditem

    location = startroom

    sdesc = "suit of armor"

    noun = 'suit' 'armor'

    adjective = 'medieval'

    ldesc =

    {

        "It's a suit of plate-mail armor that looks suitable

        for a very tall knight. ";

 

        /* if I'm holding the axe, so note */

        if (axe.isIn(self))

            "The armor is posed with a huge battle-axe held

            at the ready. ";

    }

    isqcontainer = true

;

 

axe: item

    sdesc = "battle axe"

    location = suitOfArmor

    noun = 'axe' 'blood' 'blade' 'edge'

    adjective = 'steel' 'battle' 'dried'

    ldesc = "It's a large steel battle axe.  A little bit of

             dried blood on the edge of the blade makes the authenticity

             of the equipment quite credible. "

;

 

portraits: fixeditem

    location = startroom

    noun = 'portraits' 'pictures' 'men'

    adjective = 'somber' 'gray-haired'

    sdesc = "portraits"

    isThem = true

    adesc = "a portrait"

;

 

Lastly, let’s add an actor, someone we can order around a little.

 

joe: Actor

    location = startroom

    noun = 'joe'

    sdesc = "Joe"

    adesc = "Joe"

    thedesc = "Joe"

    isHim = true

    actorDesc = "Joe is here, looking for lerts."

    actorAction(v, d, p, i) = {}

;

 


Defining A Surface

 

Now we can code the containment class objects. We begin by defining those that behave like surfaces.

 

chessBoard: containment

    issurface = true

    location = startroom

    noun = 'board' 'chessboard'

    adjective = 'chess' 'checker' 'chequer'

    sdesc = "chess board"

;

 

 

The issurface attribute indicates whether this object should behave like a surface. Set this attribute to true if you want the object to behave like a surface; otherwise leave it nil (default).

 

Things can be put on this object and taken off of it. An object is said to be “on”, rather than “in” this object.

 

If enterable, an actor may board and unboard this object using

 

Board obj, get on obj, get onto obj, sit on obj, lie on obj

Get off obj, get off of obj, stand <up>

 

This attribute “inherits” from other surface-related attributes. Specifically, an object whose isqsurface attribute is set to true will return (issurface == true)

 

 

Like containment objects in both ADV.T and Inform, the containment.t containment class should not define an object with both surface and container attributes. This is because the class uses one contents list and content-objects do not keep track of their disposition relative to their location. Instead this is left up to the containment object.

 

Let’s add a red queen for our chessboard. The queen is simply an ordinary item:

 

redQueen: item

    location = chessBoard

    noun = 'queen'

    adjective = 'red'

    isHer = true

    sdesc = "red queen"

    ldesc = "She's a fierce little chess piece."

;

 

If we take the queen, we get the following message.

 

>take queen

Taken.

 

And if we put the queen back on the chessboard we get this message.

 

>put the red queen on the chessboard

Done.

 

We can also examine the chessboard.

 

>x chessboard

On the chess board you see a red queen.

 

If we try to get on the chessboard we get this message.

 

>get on chessboard

That's not something you can enter.

 

As defined above, the chessboard is not enterable. This means that actors cannot board or enter the object, although items and movableActors can be placed in containers or on surfaces. By default containment class objects that have surface and container behaviours are not enterable.


Defining A Container

 

In the Entryway we have a ceramic jar with a lit candle inside.

 

jar: containment

    iscontainer = true

    location = startroom

    noun = 'jar'

    adjective = 'lidless' 'ceramic'

    sdesc = "lidless ceramic jar"

;

 

By defining an iscontainer attribute in a containment class object we are indicating that the object should behave like a container.

 

 

The iscontainer attribute indicates whether this object should behave like a container. Set this attribute to true if you want the object to behave like a container; otherwise leave it nil (default).

 

Things can be put in this object and taken out of it. An object is said to be “in”, rather than “on” this object.

 

If enterable, an actor may board and unboard this object using

 

Board obj, get in obj, get into obj, enter obj (explicit)

Get out obj, get out of obj, exit obj (explicit)

 

The enter and exit commands require the explicit use of the direct object to differentiate them from room direction commands.

 

This attribute “inherits” from other container-related attributes. Specifically, an object whose isqcontainer, isopenable, islockable, or istransparent attributes are set to true will return (iscontainer == true)

 

 

We also code a lightsource, to be used in locations that require light.

 

candle: lightsource

    location = jar

    noun = 'candle'

    sdesc = "candle"

    islit = true

;

 


Defining An Openable

 

Openable objects are a special form of container useful for creating bottles, bags, boxes, and other objects that you might want to open or close. The Entryway has one non-transparent openable, a bag:

 

bag: containment

    isopenable = true

    location = startroom

    noun = 'bag'

    sdesc = "bag"

    insideDesc = "%You're% inside a close confining space."

    ldesc = {

        "It looks like a sleeping bag. ";

        pass ldesc;

    }

;

 

 

The isopenable attribute indicates whether this object should behave like an openable. Set this attribute to true if you want the object to behave like an openable; otherwise leave it nil (default).

 

This object behaves like a container that can be opened and closed.

 

If enterable, it behaves like a container.

 

This attribute “inherits” from other container-related attributes. Specifically, an object whose islockable, or istransparent attributes are set to true will return (isopenable == true)

 

 

 

All containment class objects have their isopen attribute set to true by default. Accessibility rules demand that an object be open in order to gain access to its contents. The definition of non-openable containment class objects should always have isopen set to true if access to their contents is desired.

 

When the openable is open we can put things in it and take things out of it. When it is closed we cannot access its contents.

 

>put red queen in the bag

Done.

 

>close the bag

Closed.

 

>take the red queen

You can't see any red queen here.

 

>open the bag

Opening the bag reveals a red queen.

 

>x the red queen

She's a fierce little chess piece.

 

>take it

Taken.

 

Defining An Enterable Surface or Container

 

The last object to be defined in the Entryway is the wooden chair. This is the first containment class object we’ve defined that permits an actor to board it.

 

chair: fixeditem, containment

    issurface = true

    isenterable = true

    location = startroom

    sdesc = "wooden chair"

    noun = 'chair'

    adjective = 'straight-backed' 'wooden'

;

 

The chair is a fixeditem – it cannot be taken. Otherwise it behaves similarly to the chessboard, except that by overriding the isenterable attribute and setting it to true we have made it an enterable surface.

 

 

The isenterable attribute is only applicable in conjunction with the issurface or iscontainer attributes. Only containment class objects with isenterable = true attributes will allow an actor to board them.

 

By default a containment class object is not enterable.

 

 

 

The View From An Enterable Surface or Container

 

We can board the chair in several different ways: sit on chair, get on chair, board chair.

 

>sit on chair

Okay, you're now sitting on the wooden chair.

 

The view from the chair is taken from a perspective as though the actor were half on and half off the chair, incorporating the main description from the room. The short description now indicates that the actor is “on” the chair.

 

Entryway, on the wooden chair

       This large, formal entryway is slightly intimidating: the walls are lined with somber portraits of gray-haired men from decades past; a medieval suit of armor, posed with battle axe at the ready, towers over a single straight-backed wooden chair. The front door leads back outside to the south. A hallway leads north.

       Joe is here, looking for lerts.

       You see a chess board, a lidless ceramic jar, and a bag here. Sitting on the chess board is a red queen. The lidless ceramic jar seems to contain a candle (providing light).

 

The Inside Description Using insideDesc

 

Containment class objects may define an insideDesc attribute that describes the view from inside the object. This is analogous to a room ldesc, and should not mention the objects contents unless they are not listed and are of special reference.

 

When an actor does a <<look>> from the vantage of an enterable containment class object the main room description excludes the actor’s location. The description of the actor’s location, the insideDesc is displayed after the display of the room’s contents by listcont() and listcontcont(). Following the enterable’s insideDesc we list the enterable’s contents via listcont() and listcontcont().

 

Suppose we put the chessboard on the chair beside us.

 

>put chessboard on chair

Done.

 

>l

Entryway, on the wooden chair

       This large, formal entryway is slightly intimidating: the walls are lined with somber portraits of gray-haired men from decades past; a medieval suit of armor, posed with battle axe at the ready, towers over a single straight-backed wooden chair. The front door leads back outside to the south. A hallway leads north.

       Joe is here, looking for lerts.

       You see a lidless ceramic jar and a bag here. The lidless ceramic jar seems to contain a candle (providing light).

       On the wooden chair you can see a chess board. Sitting on the chess board is a red queen.

 

 

Since our chair does not define an insideDesc nothing is displayed for this. But the last line is the contents listing for the chair using listcont() and listcontcont().

 

Next we define the two other rooms of the house, the hallway and the kitchen. These are simple containment class rooms, and are defined similarly to the Entryway.

 

hallway: containment

    lightsOn = true

    sdesc = "Hallway"

    ldesc = "This broad, dimly-lit corridor runs north and south. "

 

    south = startroom

    north = kitchen

;

 

kitchen: containment

    lightsOn = true

    sdesc = "Kitchen"

    ldesc = "This is a surprisingly cramped kitchen, equipped with

             antiques: the stove is a huge black iron contraption,

             and there doesn't even seem to be a refrigerator.  A

             hallway lies to the south. "

    south = hallway

;

 

Our Kitchen contains a few very interesting containment class objects. The first is a stove, hiding a loaf of bread.

 

stove: fixeditem, containment

    isopenable = true

    location = kitchen

    noun = 'stove' 'oven' 'contraption' 'door'

    adjective = 'huge' 'black' 'iron' 'stove'

    sdesc = "stove"

    ldesc =

    {

        "It's a huge black iron cube, with a front door that swings

        open sideways.  The door is currently <<

        self.isopen ? "open" : "closed">>. ";

 

        /* list my contents if there's anything inside */

        if (self.isopen && itemcnt(self.contents) != 0)

            "Inside the stove you can see <<listcont(self)>>. ";

    }

 

    /* it starts off closed */

    isopen = nil

;

 

Like the chair, we’ve made this a fixeditem, so it can’t be taken. We’ve also defined its isopen attribute to be nil, so its initial state is closed, and we don’t see the bread until we open the stove.

 

loaf: fooditem

    location = stove

    sdesc = "loaf of bread"

    ldesc = "It's a fresh loaf with a golden-brown crust. "

    noun = 'loaf' 'bread' 'crust'

    adjective = 'fresh' 'golden-brown' 'brown'

    doEat(actor) =

    {

        "You tear off a piece and eat it; it's delicious.  You tear off

        a little more, then a little more, and before long the whole loaf

        is gone. ";

 

        /* make the bread vanish by moving it to "nil" */

        self.moveInto(nil);

    }

;

 

Our kitchen also has a table, a simple enterable surface, to demonstrate the use of the insideDesc attribute:

 

table: containment

    issurface = true

    isenterable = true

    location = kitchen

    noun = 'table'

    sdesc = "table"

    insideDesc = "The surface of the table is small and cramped."

;

 

We’ll add an apple, to keep the doctor away!

 

apple: fooditem

    location = table

    noun = 'apple'

    sdesc = "apple"

    adesc = "an apple"

;

 

The view from the table would look something like this, once we’ve added the glass bottle definition:

 

>get on table

Okay, you're now on the table.

 

>l

Kitchen, on the table

       This is a surprisingly cramped kitchen, equipped with antiques: the stove is a huge black iron contraption, and there doesn't even seem to be a refrigerator. A hallway lies to the south.

       The surface of the table is small and cramped.

       On the table you can see a glass bottle and an apple.

 

The first four lines are the kitchen “room” description, the fifth is the table inside description, and the sixth is table’s own “room” listing.

 


Defining A Transparent

 

The glass bottle is a transparent openable. This means we can see the contents of the bottle even when it is closed.

 

bottle: containment

    istransparent = true

    location = table

    sdesc = "glass bottle"

    noun = 'bottle'

    adjective = 'glass'

;

 

>put apple in bottle

Done.

 

>close bottle

Closed.

 

>x bottle

The glass bottle is closed. In the glass bottle you see an apple.

 

>take apple

You'll have to open the glass bottle first.

 

>open bottle

Opened.

 

>take apple

Taken.

 

Defining a Permeable

 

The permeable resembles the transparentItem in many ways, but they have some very significant differences: A permeable always allows audible, olfactory, and reachable access. (For transparentItems these particular forms of access are dependent upon the object’s isopen attribute.)  Permeables can be used as cages, ventilators, or even as high shelves. The following defines a cage that an actor can throw objects outside of, but cannot retrieve them until he has opened the cage.

 

cage: permeable

    location = greatHall

    noun = 'cage'

    isenterable = true

    isopen = nil

    sdesc = "cage"

    insideDesc = "The inside of the cage is cramped."

;

 

Notice that objects thrown from inside the cage will default to landing inside the cage. This probably ought to be modified so that they fall into the greatHall. Note, too, that once the cage is open access to the outside that requires “touch” is available and the actor can take things. Such a cage provides an interesting paradox, however, since <<bob, throw me the keys>> will work, where <<bob, give me the keys>> will not.

 

Defining An Enterable Openable

 

The last two objects in the house come out of the Inform Designers Manual, chapter IV: The Model World, section 14: Things to enter, travel in and push around.

 

If the player gets into a container and then closes it, the effect is like being in a different location. (Unless the container has the transparent attribute and is therefore see-through.) The interior may be dark, but if there's light to see by, the player will want to see some kind of room description. In any case, many enterable objects ought to look different from inside or on top. Inside a vehicle, a player might be able to see a steering wheel and a dashboard, for instance. On top of a cupboard, it might be possible to see through a skylight window.

 

For this purpose, any enterable object can provide an inside_description, which can be a string or a routine to print one, as usual. If the exterior location is still visible, then the "inside description'' is added to the normal room description, and otherwise it becomes that description. As an extreme example, suppose that the player gets into a huge cupboard, closes the door behind her and then gets into a plastic cabinet inside that. The resulting room description might read like so:

 

The huge cupboard (in the plastic cabinet)

It's a snug little cupboard in here, almost a room in itself.

 

In the huge cupboard you can see a pile of clothes.

 

The plastic walls of the cabinet distort the view.

 

The second line is the inside_description for the huge cupboard, and the fourth is that for the plastic cabinet.

 

Our hallway is a little Spartan, so we’ll add the cupboard and cabinet objects to it.

 

hugeCupboard: containment

    islockable = true

    isenterable = true

    location = hallway

    noun = 'cupboard'

    adjective = 'huge'

    sdesc = "huge cupboard"

    insideDesc = "It's a snug little cupboard in here,

        almost a room in itself."

;

 

plasticCabinet: containment

    isopenable = true

    istransparent = true

    isenterable = true

    location = hugeCupboard

    noun = 'cabinet'

    adjective = 'plastic'

    sdesc = "plastic cabinet"

    insideDesc = "The plastic walls of the cabinet distort the view."

;

 

 

>get in cupboard

Okay, you're now in the huge cupboard.

 

The View From An Enterable Openable

 

Once we’re inside the cupboard we can take a look around. Assuming its open the view is not unlike that of an enterable container or enterable surface.

 

Hallway, in the huge cupboard

       This broad, dimly-lit corridor runs north and south.

       It's a snug little cupboard in here, almost a room in itself.

            In the huge cupboard you can see a plastic cabinet and a pile of clothes.

 

Closing the cupboard is a mistake, unless we have a lightsource!

 

>close cupboard

Closed.

 

>l

It's pitch black.

 

Our status line reads “In the dark.” And most actions are unavailable to us. However, you can always open the cupboard and retrieve the candle. At that point the view changes quite a bit.

 

Huge cupboard

       It's a snug little cupboard in here, almost a room in itself.

       You see a plastic cabinet and a pile of clothes here.

 

The “It’s a snug little cupboard…” line is the inside description for the cupboard. Now we’re ready to get in the plastic cabinet and take a look. But first we’ll put the candle in the cabinet, to give us some perspective.

 

Huge cupboard, in the plastic cabinet

       It's a snug little cupboard in here, almost a room in itself.

       You see a pile of clothes here.

       The plastic walls of the cabinet distort the view.

In the plastic cabinet you can see a candle (providing light).

 

And finally we close the cabinet, which is transparent. Notice that the display changes subtly from that above.

 

Huge cupboard, in the plastic cabinet

       It's a snug little cupboard in here, almost a room in itself.

       In the huge cupboard you can see a pile of clothes.

            The plastic walls of the cabinet distort the view.

       You see a candle (providing light) here.

 

 


Defining A Lockable

 

Notice that the cupboard defines an islockable attribute, which is probably sensible for a hall cupboard.

           

 

The islockable attribute indicates whether this openable object should behave like a lockable object. Set this attribute to true if you want the object to behave like a lockable; otherwise leave it nil (default).

 

This object behaves like an openable that can be locked and unlocked.

 

If enterable, it behaves like an openable.

 

If the object defines a mykey attribute then a key is required; otherwise no key is required to lock and unlock the object.

 

 

 

We can lock and unlock the cupboard without a key:

 

>lock cupboard

You'll have to close the huge cupboard first.

 

>close it

Closed.

 

>lock it

Locked.

 

>open it

It's locked.

 

>get in cupboard

You'll have to open the huge cupboard first!

 

>unlock it

Unlocked.

 

>open it

Opening the huge cupboard reveals a plastic cabinet and a pile of clothes.


Defining A Vehicle

 

Now we’re ready to move outside of the house and demonstrate the containment class isvehicle attribute.

 

First, we code the front door. This is not a containment class object, but a door class object, which simplifies the coding and eliminates the need to code two separate doorways. Notice that our frontDoor is found in both the startroom and the frontYard. This second location is an extension to the workbench sample.

 

frontDoor: door

    foundin = [ startroom, frontYard ]

    noun = 'door'

    adjective = 'front'

    sdesc = "front door"

;

 

Our frontYard is simply a containment “room” with the lightsOn attribute set to true.

 

frontYard: containment

    lightsOn = true

    toRiver = bendInRiver

    sdesc = "Front Yard"

    ldesc = "%You're% standing in a broad front yard. The front door of

        house is to the north. "

    north = frontDoor

;

 

A vehicle is simply a “room” within a room that is moved from one location to another. The defining features of a vehicle are certain restrictions on actions when inside the vehicle, and the lack of a “room” description when boarding.

 

In addition, a vehicle can have the behaviours of a surface or container if it defines any of the issurface, isqsurface, iscontainer, isqcontainer, isopenable, islockable, or istransparent attributes.

 

Our raft comes out of the TADS Author’s Manual, chapter 7: Advanced Techniques; Nested Room Vehicles:

 

raft: containment

    isvehicle = true

    location = frontYard

    sdesc = "inflatable rubber raft"

    adesc = "an inflatable rubber raft"

    noun = 'raft'

    adjective = 'inflatable' 'rubber'

    isinflated = nil

    ldesc = "It's an inflatable rubber raft.  Currently,

        it's <<self.isinflated ? "inflated" : "not inflated">>. "

    verDoTake( actor ) =

    {

        if ( self.isinflated )

            "You'll have to deflate it first. ";

        else

            pass doTake;

    }

    verDoInflateWith( actor, iobj ) =

    {

        if ( self.isinflated) "It's already inflated! ";

    }

    doInflateWith( actor, iobj ) =

    {

        if ( self.isIn( actor ) )

            "You'll have to drop it first. ";

        else

        {

            "With some work, you manage to inflate the raft. ";

            self.isinflated = true;

        }

    }

    verDoDeflate( actor ) =

    {

        if ( !self.isinflated )

            "It's as deflated as it can get. ";

    }

    doDeflate( actor ) =

    {

        "You let the air out, and the raft collapses to a

        compact pile of rubber. ";

        self.isinflated = nil;

    }

    doBoard( actor ) =

    {

        if ( self.isinflated )

            pass doBoard;

        else

            "You'll have to inflate it first. ";

    }

    doUnboard( actor ) =

    {

        if ( isclass( self.location, riverRoom ) )

            "Please keep your hands and arms inside the raft

            at all times while the raft is in motion. ";

        else

            pass doUnboard;

    }

    out =

    {

        if ( isclass( self.location, riverRoom ) )

        {

            "You can't get out until you've landed the raft. ";

            return( nil );

        }

        else

            pass out;

    }

    verDoLaunch( actor ) = {}

    doLaunch( actor ) =

    {

        if ( isclass( self.location, riverRoom ) )

            "You're already afloat, if you didn't notice. ";

        else if ( self.location.toRiver == nil )

            "There doesn't appear to be a suitable waterway here. ";

        else if ( parserGetMe().location != self )

            "You'll have to get in the raft first. ";

        else

        {

            "The raft drifts gently out into the river. ";

            notify( self, &moveDaemon, 0 );

            self.counter = 0;

            self.moveInto( self.location.toRiver );

        }

    }

    verDoLand( actor ) = {}

    doLand( actor ) =

    {

        if (not  isclass( self.location, riverRoom ) )

            "You're already fully landed. ";

        else if ( self.location.toLand == nil )

            "There's no suitable landing here. ";

        else

        {

            "You steer the raft up onto the shore. ";

            unnotify( self, &moveDaemon );

            self.moveInto( self.location.toLand );

        }

    }

    moveDaemon =

    {

        "\bThe raft continues to float downstream. ";

        self.counter++;

        if ( self.counter > 1 )

        {

            self.counter = 0;

            if ( self.location.downRiver == nil )

            {

                "The raft comes to the end of the river, and lands.";

                self.moveInto( self.location.toLand );

                unnotify( self, &moveDaemon );

            }

            else

            {

                self.moveInto( self.location.downRiver );

                self.location.riverDesc;

            }

        }

    }

;

 

Next we add all the other objects and grammar required to make our sojourn down river a pleasant one.

 

inflateVerb: deepverb

    sdesc = "inflate"

    verb = 'inflate' 'blow up'

    ioAction( withPrep ) = 'InflateWith'

    prepDefault = withPrep

;

 

deflateVerb: deepverb

    sdesc = "deflate"

    verb = 'deflate'

    doAction = 'Deflate'

;

 

pump: item

    sdesc = "pump"

    location = frontYard

    noun = 'pump'

    verIoInflateWith( actor ) = {}

    ioInflateWith( actor, dobj ) =

    {

        dobj.doInflateWith( actor, self );

    }

;

 

launchVerb: deepverb

    sdesc = "launch"

    verb = 'launch'

    doAction = 'Launch'

;

 

landVerb: deepverb

    sdesc = "land"

    verb = 'land'

    doAction = 'Land'

;

 

According to the Manual:

 

Note that we'll expect each river room to have a property, riverDesc,

which displays a message when the raft drifts into that room.

The moveDaemon method will keep the raft in each river room for two turns,

then move the raft to the next river room, calling riverDesc to note the entry.

When the raft comes to the end of the river, the method will automatically land

the raft; this means that the last river room must have a non-nil toLand property.

You could alternatively put in a waterfall or other special effect when reaching

the end of the river.

 

To build a river, all you have to do is define a series of rooms of class riverRoom,

and point the downRiver property in each to the next room downriver. Landings are built by setting the toRiver and toLand properties of the landing room and corresponding river room, respectively, to point to each other.

 

For this we devise a riverRoom class, which is a containment class with a few extra details.

 

class riverRoom: containment

    lightsOn = true

    riverDesc = ""

    toRiver = nil

    toLand = nil

    downRiver = nil

    ldesc = { self.riverDesc; }

;

 

bendInRiver: riverRoom

    sdesc = "Bend In River"

    ldesc = "The river winds its way gently past your house."

    riverDesc = "The current is slow moving and serene at this spot along

        the river."

    toLand = frontYard

    downRiver = rapids

;

 

rapids: riverRoom

    sdesc = "Rapids"

    ldesc = "The waters rushing through the narrow canyons drown out all

        other sounds."

    riverDesc = "The water suddenly gets very choppy and dangerous

    here."

    downRiver = broadRiver

;

 

broadRiver: riverRoom

    sdesc = "Broad River"

    ldesc = "This could easily be a lake, the water is as calm as a mill-

        pond."

    riverDesc = "The river slows as it broadens out at this point."

    toLand = embankment

;

 

embankment: containment

    lightsOn = true

    sdesc = "Embankment"

    ldesc = "You're standing on the embankment of a broad river."

;


On Boarding and Unboarding Enterables and Vehicles

 

One last thing needs mentioning concerning the boarding and unboarding of enterable surfaces and containers. The containment class allows you to traverse the containment lists of objects, so that you can get on, get off of, get in, or get out of objects that are within other objects.

 

For example, suppose we are in the hallway and issue the command to get into the cabinet. Since the cabinet is inside the cupboard the actor first travels to the cupboard, and then to the cabinet.

 

>get in cabinet

(getting into the huge cupboard)

Okay, you're now in the plastic cabinet.

 

The reverse happens if we should wish to leave the cupboard while in the cabinet.

 

>get out of cupboard

(getting out of the plastic cabinet)

Okay, you're no longer in the huge cupboard.

 

In general, you can board and unboard containment class objects in this fashion as long as the target objects are within the scope of the actor. What this means is simply this. Suppose you have a table in the hallway that the actor is sitting on. A room description of the hallway will indicate that the cupboard seems to contain a cabinet. The player can enter >board cabinet and the actor will travel to the cabinet. However, from the vantage of the cabinet the table does not appear in the room display, and so >board table results in a “You don’t see any table here.” Message. This has been implemented as a “what you see is what you can get” style of scope, which can be changed via the passToLocation() and passToContents() methods used by sense.t

 

 


8. Object Reactions

 

Sense 3.1 employs a simple mechanism for generating object reactions. The system confines its scope to the top-level location of the actor and not to any extended locations. The sequence of object reactions is as follows:

 

 

Object

Sequence

PreAction

(lvl == 1)

PostAction

(lvl == 2)

EndCommand

(lvl == 3)

actor

actorPreAction

actorPostAction

actorEndCommand

loc

roomPreAction

roomPostAction

roomEndCommand

object

scopePreAction

scopePostAction

scopeEndCommand

iobj

ioPreAction

ioPostAction

ioEndCommand

dobj

doPreAction

doPostAction

doEndCommand

 

 

The loc is calculated as the scope ceiling for the actor and is based on visibility rules. If the actor is in a closed non-transparent container then the loc is that container. Similarly, the scopeXXXXX reactions objects are determined for this location using visibility rules. If an abort statement is issued at the preAction or postAction stage then object reactions for the following stages are bypassed, even through postAction and endCommand are still executed by the parser.

 

Processing continues along this sequence unless one of the methods issues an exitobj, exit, or abort statement.

 

No parameters are passed to these methods, but they have complete access to the command object, including the status of the command during the postAction and endCommand stages.

 


9. Library-defined Functions and Methods

 

Access Rules

 

access_audible(o)

Purpose:         An object meets the requirements for crossing the contents threshold for audibility if it meets the following conditions: (o.contentsAudible).

Calls:               o.contentsAudible

Return             true;

                        nil;

 

access_olfactory(o)

Purpose:         An object meets the requirements for crossing the contents threshold for “smellability” if it meets the following conditions: (o.contentsOlfactory).

Calls:               o.contentsOlfactory

Return:            true;

                        nil;

 

access_qAudible(o)

Purpose:         An object meets the requirements for crossing the contents threshold for qAudibility if it meets the following conditions: (access_audible(o) && !o.isqAudible).

Calls:               access_audible()

                        o.isqAudible

Return:            true;

                        nil;

 

access_qOlfactory(o)

Purpose:         An object meets the requirements for crossing the contents threshold for “quiet smellability” if it meets the following conditions: (access_olfactory(o) && !o.isqOlfactory).

Calls:               access_olfactory()

                        o.isqOlfactory

Return:            true;

                        nil;

 

access_qVisible(o)

Purpose:         An object meets the requirements for crossing the contents threshold for qVisibility if it meets the following conditions: (access_visible(o) && !o.isqVisible)

Calls:               access_visible()

                        o.isqVisible

Return:            true;

                        nil;

 

access_reachable(o)

Purpose:         An object meets the requirements for crossing the contents threshold for reachability if it meets the following conditions: (o.contentsReachable).

Calls:               o.contentsReachable

Return:            true;

                        nil;

 

access_touchable(o)

Purpose:         An object meets the requirements for crossing the contents threshold for touchability if it meets the following conditions: (o.contentsTouchable).

Calls:               o.contentsTouchable

Return:            true;

                        nil;

 

access_visible(o)

Purpose:         An object meets the requirements for crossing the contents threshold for visibility if it meets the following conditions: (o.contentsVisible).

Calls:               o.contentsVisible

Return             true;

                        nil;

 

Path Building And General Path Information Functions

 

buildClist(o)    

            Purpose:         Build the clist (containment list) for the object.                      

            Calls:               none

Return:            clist;

 

path(vantage, target)  

            Purpose:         Build the plist (path list) between the vantage and target.

            Calls:               buildClist()                              

Return:            plist      format: [ lcc, path_list ]

 

getPlist(plist, flag)

Purpose:         Returns the object or list corresponding to the plist and PTH_XXXX constant passed.

            Calls:               recursive

            Return:            lcc;

                                    path;

                                    vantage;

                                    target;

                                    tloc;

                                    nil;

 

inPlist(plist, o, flag)

            Purpose:         Determines if o is found in the specified part of the plist.

            Calls:               getPlist()

            Return:            true;

                                    nil;

 

Path Validation Functions

 

blocksPath(plist, filter)           

            Purpose:         Validate the path using the filter.

            Special:           sets target.accessfailed attribute [ filter, failloc, &passToXXXX ]      

Return             failloc;

nil;

 

checkAccessibility(a, v, d, p, I)

Purpose:         Validate the command using accessibility rules of the model world.            

Calls:               testScope()

                        v.cantReach()

Return             true;

nil;

 

proximity(vantage, target)

Purpose:         Produces a flag indicating the proximity of the target to the vantage and a list of all valid filters for the path between the two.                        

Calls:               path()

                        getPlist()

                        blocksPath()

Return             [ prx_flag vsList ]

 

testScope(vantage, target, filter)

Purpose:         Determine if the target is in the scope of the vantage for the given filter. If no filter is passed then determines if the object is sensible by at least one access rule.                      

Calls:               path()

                        blocksPath()

Return             true;

nil;       

 

scope(vantage, filter) 

Purpose:         Return a list of objects that are in scope of the vantage for the given filter.                          

Calls:               testScope()

Return:            scope_list;

 

loopOverScope(prop, vantage, filter) 

Purpose:         Loops over every object in the scope of the vantage for the given filter executing property.

            Calls:               testScope()                

Return:            none expected.

 

Disambiguation Methods

 

o.validActor(actor, prep, othobj)         

Purpose:         Used by the parser in disambiguating the actor element of a command. The default test is for reachability.    

Calls:               testScope()

Return             true;

                        nil;

 

v.validXoList(actor, prep, othobj)       

Purpose:         Produce a list of valid objects to enhance the performance of the disambiguation process. Sense.t uses this method to store command elements.                  

Calls:               none

Special:           Stores command.actorPtr, command.prepPtr, and either command.iobjPtr or command.dobjPtr depending upon which method has been called.

Return             nil; passing the original parsed noun phrase list to the validXo() methods.

 

v.validXo(actor, obj, seqno)   

Purpose:         Validate the direct or indirect object with respect to model world access rules / other rules during the parser disambiguation phase. Returns true if the object is sensible to the actor by any filter; otherwise calls checkRequires and returns nil.                 

Calls:               testScope()

Special:           Must set o.accessfailed attribute (done in blocksPath() in order for v.cantReach() to obtain the necessary information.)

Return             true;

nil;

 

o.isVisible(vantage)    

Purpose:         Used by the parser disambiguation phase to determine how to report a disambiguation or accessibility error. By returning a default of true Sense bypasses the parser display “I don’t see any foo here.” This allows the error to be handled by the cantReach() mechanism.    

Calls:               none

Return             true;     (default)

 

Accessibility Failure Handling Methods

 

v.cantReach(actor, dolist, iolist, prep)

Purpose:         Redirects the failed access to the appropriate cantXXXX method.  

Calls:               cantSee()

                        cantHear()

                        cantSmell()

                        cantReach()

Uses:               obj.accessfailed attribute

Return             none expected.

 

 

o.cantSee(vantage, target)    

Purpose:         Displays a message for the failing location when access_visible access has failed for an object

            Uses:               command.cantReachWords

Return             none expected

 

o.cantHear(vantage, target)   

Purpose:         Displays a message for the failing location when access_audible access has failed for an object

            Calls:               getAccessfailed()

                                    testScope()

                                    cantSee()       

Return             none expected

 

o.cantSmell(vantage, target)

Purpose:         Displays a message for the failing location when access_olfactory access has failed for an object

            Calls:               getAccessfailed()

                                    testScope()

                                    cantSee()       

Return             none expected

 

o.cantReach(vantage, target)            

Purpose:         Displays a message for the failing location when access_reachable access has failed for an object

            Calls:               getAccessfailed()

                                    testScope()

                                    cantSee()       

Return             none expected

 

getAccessfailed(target, flag)              

Purpose:         Retrieves the specified element of the target.accessfailed attribute corresponding to one of the GAF_XXXX flags. The accessfailed attribute is a list set in blocksPath(). If no element is found the function returns nil.

            Uses:               target.accessfailed    

Return             access_rule;

                        failloc;

                        pass-to-method;

                        nil;

 

Room Display Methods

 

o.nrmLkAround(actor, verbosity)

Purpose:         Displays the actor’s normal location description and contents for visible listable objects.

Calls:               path()

                        blocksPath()

                        o.xtndLkAround()                    

Return             none expected

 

o.xtndLKAround(actor, verbosity)                  

Purpose:         Displays the actor’s extended location description and contents for visible listable objects.

Calls:               none

Return             none expected

 

o.nrmLnAround(actor, verbosity)                   

Purpose:         Displays the actor’s normal location description and contents for audible listable objects.

Calls:               path()

                        blocksPath()

                        o.xtndLkAround()

                        o.dispcont()    

Uses:               command.verbPtr      

Return             none expected

 

o.xtndLnAround(actor, verbosity)                   

Purpose:         Displays the actor’s extended location description and contents for audible listable objects.

Calls:               o.dispcont()

Return             disptot;

 

o.nrmSmAround(actor, verbosity)                 

Purpose:         Displays the actor’s normal location description and contents for olfactory listable objects.

Calls:               path()

                        blocksPath()

                        o.xtndLkAround()

                        o.dispcont()                

Return             none expected

 

o.xtndSmAround(actor, verbosity)                 

Purpose:         Displays the actor’s extended location description and contents for olfactory listable objects.

Calls:               o.dispcont()

Return             disptot;

 

o.dispcont(actor, filter, listedAttr, descAttr)   

Purpose:         Displays the contents of the object using a given actor, filter, isListedXXXX attribute, and sense description.

            Calls:               bubbleScope()

                                    path()

                                    blocksPath()

Return             disptot;

nil;

 

locale(actor, loc, access_rule)

Purpose:         Displays the xtndXXAround of the loc using a given actor and access_rule. This display temporarily sets the location as isListedXXXX = true and isqXXXXX = nil; thus displaying the extended ldesc and contents.

            Calls:               loc.xtndXXAround()

Return             no return expected

 

Extended Room Scope Methods

 

bubbleScope( vantage, limit, filter )

Purpose:         Use this function when you wish to obtain a list of all objects that are valid for a given vantage and access_filter. The parameters are as follows:

           

vantage          object whose scope is being determined.     

 

limit                 scope ceiling limit.  The highest-level object you wish the scope ceiling to be determined for; otherwise this should be nil.

 

filter                Set this to the access_filter that is the level of the scope for which you wish to determine an object’s scope list.

Calls:               scopeCeiling()

bubbleScopeList()

runRoutines()

            Return:            list of objects in scope within top-level location

 

Examples:

 

Suppose in our startroom we have a candle inside of a closed glass jar.

 

scope( candle, nil, &access_reachable )

 

Will return the list:

 

[ glassJar candle ]

 

While

 

scope( candle, nil, &access_visible )

 

Will return the list:

 

[ startroom glassJar candle theFloor Me ]

 

 

scopeCeiling( vantage, limit, filter )

Purpose:         Use this function when you wish to obtain an object which is the scope ceiling for the given vantage, limit, and access_filter. The parameters are the same as those for scope().

Calls:               none

Return:            object that is scope ceiling

 

Examples:

 

Using our example above, if we wanted to constrain our scope ceiling to the glassJar we would set limit to the glassJar, thus:

 

            Scope( candle, glassJar, &access_visible )

 

This would return visibility limited to just those objects within the glassJar:

 

            [ glassJar candle ]

 

Notice that

 

            Scope( glassJar, glassJar, &access_visible )

 

Will return

 

            [ glassJar candle ]

 

This is what we would expect. However, the following:

 

            scope( startroom, glassJar, &access_visible )

 

Would return

 

            [ startroom glassJar candle theFloor Me ]

 

This is because scopeCeiling begins its determination from startroom, working its way up the object-tree. Since glassJar does not match the vantage or any ancestor in the object-tree the limit is ignored and a valid ceiling is determined for startroom using the access_filter as the only constraint. It is important to bear this in mind when providing a limit to scopeCeiling() that its primary function is to produce a valid scope ceiling for the vantage given the scope-level and it will do so regardless of the value of limit.

 

 

If the limit does not match the vantage or any ancestor in the object-tree the limit is ignored and a valid ceiling is determined for the vantage using only the access_filter as the constraint.

 

 

Using our example, if we chose startroom as the scope ceiling limit and the glassJar is closed then the following:

 

            scope( candle, startroom, &access_reachable )

 

Would return

 

            [ glassJar candle ]

 

Here scope() begins with a scope ceiling of glassJar, not with startroom, because the valid scope ceiling for the candle is further constrained by the access_filter, which override the limit constraint.

 

bubbleScopeList( loc, vantage, ceiling, filter )

Purpose:         This is a recursive function that builds and returns a list of all objects for a given vantage, scope ceiling, and scope by working its way down the object-tree. The parameters are as follows:

 

Loc                 

 

vantage          object whose scope is being determined.     

 

limit                 scope ceiling limit.  The highest-level object you wish the scope ceiling to be determined for; otherwise this should be nil.

 

filter                Set this to the access_filter that is the level of scoping you wish to determine an object’s scope list for.

            Calls:               recursive

            Return:            list of scopable objects, exclusive of top-level location and floating items.

 

inBubbleScope(  vantage, target, limit, filter )

Purpose:         Use this function to determine if the target is in the scope of the vantage for a given limit and access_filter. If this is the case then the function returns true; otherwise it returns nil. This function uses scope() in making its determination, so objects such as floatingItem class objects will be taken into consideration. The parameters are as follows:

 

vantage          object whose scope is being evaluated.        

 

target              object being examined to determine if it is within the scope of the vantage.

 

limit                 scope ceiling limit.  The highest-level object you wish the scope ceiling to be determined for; otherwise this should be nil.

 

filter                set this to the access_filter that is the level of scoping you wish to determine an object’s scope list for.

            Calls:               bubbleScope()

            Return             true;

                                    nil;

 


Appendix: Modifications To ADV.T & STD.T

 

Sense makes modifications to several of the functions and classes of ADV.T and STD.T to incorporate the new scoping mechanisms. These modifications allow Sense to plug into the standard ADV.T and STD.T libraries with minimal changes to the existing mechanism. This is a brief summary of modifications to the standard behaviours of ADV.T and STD.T.[1]

 

mainRestore(fname)

 

This function has been modified to call parserGetMe().location.senseAround()

 

thing.verifyRemove()

 

Modifies verifyRemove() to 'bubble up' verGrab() check, but only as high as the scope-ceiling. This allows us to grab things when we're inside closed locations, such as enterables[2]

 

thing.isVisible(vantage)

 

The method now returns true for all cases. We want the verb.cantReach() function to handle the case where the parser’s validXo list and the verification list are both empty.

 

room.enterRoom(actor)

 

This method has been modified to call self.senseAround(actor, (!self.isseen) || global.verbose) instead of self.lookAround(). We pass the actor as a parameter in order to facilitate building a path between actor and other top-level locations .thurough

 

room.lookAround(actor, verbosity, sroot)

 

This method is modified to take the actor and sroot parameter. If sroot is true then we display the status root; otherwise we suppress it. This is necessary for cases when the method is called from room.senseAround(), when we don’t want to display the status root from this method; or when it’s called directly, for the command <<look>>, where we want the status root displayed.

 

room.nrmLkAround(actor, verbosity)

 

This method is modified to take the actor as a parameter. We use lookList to add the parserGetMe() and remove the actor from the self.contents. This way we can display the actorDesc based on the viewpoint of the actor, not the parserGetMe(). We also loop through each tlist location here, calling xtndLkAround() for each valid path.

 

deepverb.validDoList(actor, prep, iobj)

 

This method returns nil. This means that the entire parsed list is passed on to the validDo().

 

deepverb.validDo(actor, obj, seqno)

 

The validDo() method should return true if the object is valid for the accessibility rule; otherwise it should return nil. ADV.T uses reachability rules to determine the accessibility of the object. Sense builds a path between the actor and the object and then calls proximity(actor, object) to determine if there is at least one valid access_filter for the path.

 

deepverb.validIoList(actor, obj, seqno)

 

This method returns nil. This means that the entire parsed list is passed on to the validIo().

 

deepverb.validIo(actor, obj, seqno)

 

The validIo() method should return true if the object is valid for the accessibility rule; otherwise it should return nil. ADV.T uses reachability rules to determine the accessibility of the object. Sense builds a path between the actor and the object and then calls proximity(actor, object) to determine if there is at least one valid access_filter for the path.

 

die()

 

This function has been changed to use parserGetMe().location.senseAround().

 

Init()

 

This function has been changed to use parserGetMe().location.senseAround().

 

global

 

Sense.t builds a command object, making the elements of the player command, plist, and noun phrase words available to the author.

 

The command object is defined as follows:

 

/*

 *  command: object

 *

 *  The command object carries all the pertinent information connected

 *  with the command, including the plist and the word-string list that

 *  the user entered for the direct and indirect objects of the command.

 */

command: object

    actorPtr        = nil

    verbPtr         = nil

    dolistPtr       = []

    dobjPtr         = nil

    prepPtr         = nil

    iolistPtr       = []

    iobjPtr         = nil

    cantReachWords  = []

    disambigPhase   = nil

;

 

The object is maintained by validXoList(), validXo(), preCommand(), verbAction(), and endCommand(). This is to ensure that the elements accurately reflect the player’s command.

 

The global.cantReachWords is the list of words corresponding to the objects which cannot be accessed and invoke the parser call to verb.cantReach().

 

The disambigPhase is set to true by preparseCmd() and set to nil by preCommand(). The disambigObjType is a numeric value representing which type of noun phrase is being evaluated at this point in the disambiguation phase. The values are:

 

            0:         none

            1:         direct object

            2:         indirect object

 

These attributes can be used in conjunction with the passToXXXX() methods, to determine whether the player command objects have been disambiguated and to help constrain the nature of access limitation.

 



[1] This chapter does not document the changes to specific verbs. These changes involve changing the class of verbs to include dorequires and iorequires access rule pointers with which to validate the path list.

[2] The enterable class was introduced in the inform.t library extension. This is an openable / room class object that allows the actor to enter it and has now been superseded by the containment class.